import type { CustomErr } from "./types";
import { ref, computed, watch, defineComponent } from "banner-vue";
import has from "lodash/has";
import forEach from "lodash/forEach";
import { runFnInVm } from "./vm";

export default defineComponent({
  props: {
    template: {
      type: String,
      default: "",
    },
    js: {
      type: String,
      default: "",
    },
    css: {
      type: String,
      default: "",
    },
    bannerData: {
      type: Object,
      default: () => ({}),
    },
  },
  setup(props) {
    const uid = ref("");
    const subCompErr = ref<CustomErr | null>(null);

    const checkVariableField = (component) => {
      // 如果需要注入一些变量，比如名字叫variable
      let data = component.data;
      if (typeof data === "function") {
        // 如果data是函数，执行后取data的结果
        // 用于行为无法预知，可能定义了错误的data，需要trycatch防崩
        // catch的异常不用关心
        try {
          data = data();
        } catch (e) {}
      }
      if (has(component, "computed.variable") || has(data, "variable")) {
        return {
          error: {
            type: "功能限制",
            msg: "禁止自定义名称是variable的computed或data，会影响系统变量的使用",
          },
          value: { hasError: true },
        };
      }
    };

    const className = computed(() => {
      return `custom-code-${uid.value}`;
    });

    const scopedStyle = computed(() => {
      if (props.css) {
        const scope = `.${className.value}`;
        const regex = /(^|\})\s*([^{]+)/g;
        // 为class加前缀，做类似scope的效果
        return props.css.trim().replace(regex, (m, g1, g2) => {
          return g1 ? `${g1} ${scope} ${g2}` : `${scope} ${g2}`;
        });
      }
      return "";
    });

    const _component = computed(() => {
      const js = (props.js || "").trim();
      const result = runFnInVm(js, {});
      const component = result.value;
      if (result.error) {
        result.error = {
          msg: result.error.toString(),
          type: "js脚本错误",
        };
        result.value = { hasError: true };
        return result;
      }

      // 如果需要注入一些变量，则对被注入的字段进行检测
      // 防止用户代码中定义了此字段，导致冲突
      const resultTmp = checkVariableField(component);
      if (resultTmp) {
        return resultTmp;
      }

      const template = (props.template || "")
        .replace(/^ *< *template *>|<\/ *template *> *$/g, "")
        .trim();

      if (template) {
        component.template = template;
        component.render = undefined;
      } else if (!component.render) {
        component.template = "<span>未提供模板或render函数</span>";
      }

      // 注入mixins
      component.mixins = [
        {
          // 注入 beforeUpdate 钩子，用于子组件重绘时，清理父组件捕获的异常
          beforeUpdate: () => {
            this.subCompErr = null;
          },
        },
      ];

      // 注入errorCaptured, 用于错误自定义组件运行时捕获
      component.errorCaptured = (err, vm, info) => {
        subCompErr.value = {
          msg: err && err.toString && err.toString(),
          type: "自定义组件运行时错误：",
        };
        console.error("自定义组件运行时错误：", err, vm, info);
      };

      // 通过computed注入一些变量，比如store中的一些字段，
      // 当store中的变量发生变化时，也会引发组件重绘
      const computed = component.computed || {};
      computed.variable = () => {
        const map = {};
        forEach(this.$store.state.variable.map, (value, key) => {
          map[key] = value;
        });
        return map;
      };
      component.computed = computed;
      return result;
    });

    watch(
      () => props.js,
      () => {
        this.subCompErr = null;
      },
    );
    watch(
      () => props.template,
      () => {
        this.subCompErr = null;
      },
    );
    watch(
      () => props.css,
      () => {
        this.subCompErr = null;
      },
    );

    return () => {
      const { error: compileErr, value: component } = _component.value;
      const error = compileErr || subCompErr.value;

      const _bannerData = props.bannerData;
      const _scopedStyle = scopedStyle.value;
      const _className = className.value;
      let errorDom;
      if (error) {
        errorDom = (
          <div
            class="error-msg-wrapper"
            style={{ position: !error ? "absolute" : "" }}
          >
            <div>{error.type}</div>
            <div>{error.msg}</div>
          </div>
        );
      }

      return (
        <div class={["code-preview-wrapper", _className]}>
          <style>{_scopedStyle}</style>
          <component bannerData={_bannerData} />
          {errorDom}
        </div>
      );
    };
  },
});
